<?php
/**
 * 捕获API异常
 *
 * Class APIException
 */
class APIException extends Exception {
    protected $_data=array();
    protected $_track;
    public function __construct($code, $msg=NULL, $data=NULL) {
        if (empty($msg)) {
            $errors=require(APPPATH.'/config/errors.php');
            $msg = isset($errors[$code])?$errors[$code]:null;
        }
        parent::__construct($msg, $code);
        $this->_data=$data;
    }
    public function getData() {
        return $this->_data;
    }
}
/**
 * 异常处理
 *
 * Class ExceptionHandler
 */
class ExceptionHandler {
    private static $is_cli, $format, $catch_all;
    public function __construct($options=array()) {
        self::$is_cli=isset($options[0])?$options[0]:false;
        self::$format=isset($options[1])?$options[1]:'html';
        self::$catch_all=isset($options[2])?$options[2]:false;
        set_exception_handler(array($this, 'handle'));
        if(self::$catch_all){
            set_error_handler(array($this, 'errorHandle'));
            register_shutdown_function(array($this, 'exitHandle'));
        }
    }

    /**
     * 捕获异常
     * @param Exception $exception
     */
    public function handle(Exception $exception) {
        //将异常写入日志
        $this->logException($exception);
        if ($exception instanceof APIException){//显示错误消息
            $this->printException($exception);
        }
        exit();
    }

    /**
     * 捕获错误信息
     *
     * @param $code
     * @param $msg
     * @param $file
     * @param $line
     * @param $trace
     */
    public function errorHandle($code=0, $msg='', $file, $line, $trace){
        $traceData=array(
            'type'=>'error',
            'file'=>$file,
            'line'=>$line,
            'trace'=>json_encode($trace)
        );
        $code=isset($code)?$code:0;
        $exception = new APIException(2000+$code,$msg,$traceData);
        $this->handle($exception);
    }

    /**
     * 捕获退出
     */
    public function exitHandle(){
        $error=error_get_last();
        if(!$error){
            return;
        }
        $message=isset($error['message'])?$error['message']:'';
        if($message){
            unset($error['message']);
        }
        $error['trace']=var_export($error,true);
        $code=isset($error['code'])?$error['code']:0;
        $exception = new APIException(1000+$code,$message);
        $this->handle($exception);
    }

    /**
     * 设为CLI运行模式
     *
     * @param $is_cli
     */
    public function setCLIRun($is_cli){
        self::$is_cli=$is_cli;
    }

    /**
     * 错误提示格式
     *
     * @param $format
     */
    public function setFormat($format){
        self::$format=$format;
    }

    /**
     * 打印异常信息
     *
     * @param APIException $exception
     */
    private function printException(APIException $exception) {
        $data=(array)$exception->getData();
        $trace=isset($data['trace'])?$data['trace']:$exception->getTraceAsString();
        if(is_array($trace)){
            $trace=print_r($trace,true);
        }
        $line="\n";
        if(!self::$is_cli){
            $line='<br/>';
            $trace=str_replace("\n",$line,$trace);
        }
        $e=<<<ERR
编号: {$exception->getCode()}{$line}
描述: {$exception->getMessage()}{$line}
ERR;
        if('production'!==ENVIRONMENT){
            $e.=<<<STR
文件: {$exception->getFile()} 第{$exception->getLine()}行{$line}
详情: <br/>{$trace}{$line}
STR;
        }
        if(!self::$is_cli){
            if(self::$format=='html'){
                show_error($e, 500, '出现异常');
            }
            else{
                $json=json_encode(array('code'=>$exception->getCode(),'msg'=>$exception->getMessage(),'data'=>array()));
                $return=preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($match) {
                    return iconv('UCS-2BE', 'UTF-8', pack('H*', $match[1]));
                }, $json);
                echo($return);
            }
        }
        else{
            echo($e);
        }
    }

    /**
     * 写入错误日志
     *
     * @param $exception
     */
    private function logException(Exception $exception) {
        $code=$exception->getCode();
        if ($code < 10000 || $code > 60000) {
            $code=99999;
        }
        $data='';
        if($exception instanceof APIException){
            $data=print_r($exception->getData(),true);
        }
        log_message('error', $code."\t".$exception->getMessage()
            ."\t". $exception->getFile()." ". $exception->getLine()
            ."\n".$data
            . "\n" . $exception->getTraceAsString());
    }

}